package proxy

import (
	"context"
	"io"
	"net/http"
	"net/url"
	"strconv"

	"github.com/gin-gonic/gin"
	log "github.com/sirupsen/logrus"
)

type ginForwarding struct {
	engine *gin.Engine
	client *http.Client
}

func newGinForwarding() *ginForwarding {
	return &ginForwarding{
		engine: customEngine(),
		client: http.DefaultClient,
	}
}

func customEngine() *gin.Engine  {
	engine := gin.New()
	engine.Use(gin.Logger(), gin.Recovery())
	return engine
}

func (f *ginForwarding) Forwarding() error {
	go f.doForwarding()
	return nil 
}
func (f *ginForwarding) DoStop() error {
	return nil
}

func (f *ginForwarding) doForwarding() {
	f.engine.NoRoute(f.forwarding)
	err := f.engine.Run(":80")
	if err != nil {
		log.Fatal(err)
	}
}

func (f *ginForwarding) forwarding(c *gin.Context) {
	serviceName := getServiceName(c.Request)
	if serviceName == "" {
		c.String(500, "cannot find service")
		return
	}
	serviceGroup := getServiceGroup(c.Request)

	ip, port, err := choose(serviceName, serviceGroup)
	if err != nil {
		c.String(500, "choose service error")
		return
	}

	nreq := c.Request.Clone(context.Background())
	defer nreq.Body.Close()

	newuri := reconstructuri(nreq.RequestURI, ip, port)
	newurl, err := url.Parse(newuri)
	if err != nil {
		c.String(500, "parse newuri error")
		return
	}

	nreq.URL = newurl
	nreq.Host = ""
	nreq.RequestURI = newuri
	if serviceGroup != "" {
		nreq.Header[ServiceGroupKey] = []string{serviceGroup}
	}

	resp, err := f.client.Do(nreq)
	if err != nil {
		c.String(500, err.Error())
		return
	}
	buildResponse(c, resp)

}

func getServiceName(r *http.Request) string {
	return r.Host
}

func getServiceGroup(r *http.Request) string {
	if groupHeader, ok := r.Header[HeaderServiceGroupKey]; ok {
		return groupHeader[0]
	}
	if cookieGroup, err := r.Cookie(ServiceGroupKey); err == nil {
		return cookieGroup.Value
	}
	return ""
}

func reconstructuri(uri string, ip string, port int) string {
	return "http://" + ip + ":" + strconv.Itoa(port) + uri
}

func buildResponse(c *gin.Context, resp *http.Response) {
	data, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Error(err)
		c.String(500, err.Error())
		return 
	}
	defer resp.Body.Close()

	c.Writer.WriteHeader(resp.StatusCode)
	header := c.Writer.Header()

	for k, v := range resp.Header {
		header[k] = v 
	}
	_, err = c.Writer.Write(data)
	if err != nil {
		c.String(500, err.Error())
	}
}
